// OpenGL Render Context
// (c) jimon game studio

#include "CRenderContext_OpenGL.h"
#include "jeMemory.h"
#include "ILogManager.h"
#include "IVariables.h"
#include "CWindow_Win.h"

#ifdef JE_RENDER_OPENGL

namespace je
{
	namespace video
	{
		//! Constructor
		CRenderContext_OpenGL::CRenderContext_OpenGL(CRender_OpenGL_Extensions * extensions,OpenGLWorkMode WorkMode)
			:Extensions(extensions),RenderWindow(NULL),FullWindowUse(false),CurrentWorkMode(WorkMode),NeedToUpdateExtensions(true)
		{
			Extensions->Grab();
		}

		//! Destructor
		CRenderContext_OpenGL::~CRenderContext_OpenGL()
		{
			if(RenderWindow)
			{
				DeleteOpenGLContext();
				RenderWindow->Drop();
				RenderWindow = NULL;
			}

			Extensions->Drop();
		}

		//! Init Use Of Context
		void CRenderContext_OpenGL::InitContext()
		{
			#ifdef JE_DEVICE_WIN

			if(!wglMakeCurrent(DeviceContext,RenderContext))
			{
				JELOG_E("Cant Activate OpenGL Render Context")
				return;
			}
			#endif

			#ifdef JE_DEVICE_LINUX
			if(!glXMakeCurrent(static_cast<Display*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_DEVICE)),*static_cast<Window*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_WINDOW)),CurrentGLXContext))
			{
				JELOG_E("Cant Activate OpenGL Render Context")
				return;
			}
			#endif


			if(!IsFullWindowUse())
				SetSize(GetWindow()->GetSize());

			if(NeedToUpdateExtensions)
			{
				Extensions->UpdateExtensions();
				NeedToUpdateExtensions = false;
			}

			Extensions->EnableVSync(VSyncFlag);

			Extensions->Enable(GL_DEPTH_TEST);
			Extensions->SetDepthFunction(GL_LESS);
			Extensions->Enable(GL_CULL_FACE);
			Extensions->CullFace(GL_BACK);

			if(RenderWindow->GetMSAA() > 0)
				Extensions->Enable(GL_MULTISAMPLE);
			else
				Extensions->Disable(GL_MULTISAMPLE);
		}

		//! DeInit Use Of Context
		void CRenderContext_OpenGL::DeInitContext()
		{
			#ifdef JE_DEVICE_WIN
			if(!wglMakeCurrent(NULL,NULL))
			{
				JELOG_E("Cant Deactivate OpenGL Render Context")
				return;
			}
			#endif

			#ifdef JE_DEVICE_LINUX
			if (!glXMakeCurrent(static_cast<Display*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_DEVICE)),None,NULL))
			{
				JELOG_E("Cant Deactivate OpenGL Render Context")
				return;
			}
			#endif

		}

		//! Set Window for render
		void CRenderContext_OpenGL::SetWindow(device::IWindow * Window)
		{
			if(RenderWindow)
			{
				DeleteOpenGLContext();
				RenderWindow->Drop();
			}

			RenderWindow = Window;

			if(RenderWindow)
			{
				RenderWindow->Grab();
				CreateOpenGLContext();
				ViewportSize = RenderWindow->GetSize();
			}
		}

		//! Get current render window
		device::IWindow * CRenderContext_OpenGL::GetWindow()
		{
			return RenderWindow;
		}

		//! Set Viewport Size
		void CRenderContext_OpenGL::SetSize(const jeVector2di & Size)
		{
			if(!FullWindowUse)
				ViewportSize = Size;
		}

		//! Get Viewport size
		const jeVector2di & CRenderContext_OpenGL::GetSize()
		{
			if(FullWindowUse)
				return RenderWindow->GetSize();
			else
				return ViewportSize;
		}

		//! If true, render context use full window client space
		void CRenderContext_OpenGL::SetFullWindowUse(u1 Use)
		{
			FullWindowUse = Use;

			if(!Use && RenderWindow)
				ViewportSize = RenderWindow->GetSize();
		}

		//! return fullwindowuse flag
		u1 CRenderContext_OpenGL::IsFullWindowUse()
		{
			return FullWindowUse;
		}

		//! Set VSync Flag
		void CRenderContext_OpenGL::SetVSyncFlag(u1 Flag)
		{
			VSyncFlag = Flag;
			Extensions->EnableVSync(VSyncFlag);
		}

		//! Return VSync Flag
		u1 CRenderContext_OpenGL::GetVSyncFlag()
		{
			return VSyncFlag;
		}

		//! Get Api Pointer
		void * CRenderContext_OpenGL::GetApiPointer(RenderContextApiPointerType Type)
		{
			switch(Type)
			{
			#ifdef JE_DEVICE_WIN
			case RCAPT_HDC:
				return (void*)&DeviceContext;
			case RCAPT_HGLRC:
				return (void*)&RenderContext;
			#endif
			#ifdef JE_DEVICE_LINUX
			case RCAPT_XCONTEXT:
				return CurrentGLXContext;
			#endif
			default:
				return NULL;
			}
		}

		//! Create OpenGL Context
		void CRenderContext_OpenGL::CreateOpenGLContext()
		{
			s32 OpenGLMajorVersion = CurrentWorkMode / 10;
			s32 OpenGLMinorVersion = CurrentWorkMode % 10;

			u1 EnableForwardContext = core::GetVariables()->GetVariablec8("OpenGL_ForwardContext");
			
			NeedToUpdateExtensions = true;

			#ifdef JE_DEVICE_WIN

			device::CWindow_Win * Win32Window = static_cast<device::CWindow_Win*>(RenderWindow);
			DeviceContext = Win32Window->GetWin32DeviceContext();

			u32 Depth = RenderWindow->GetDepth();
			const jeVector2di & Size = RenderWindow->GetSize();

			PIXELFORMATDESCRIPTOR pfd =
				{
					sizeof(PIXELFORMATDESCRIPTOR),
					1,
					PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,// | PFD_SWAP_EXCHANGE,
					PFD_TYPE_RGBA,
					Depth,
					0, 0, 0, 0, 0, 0,
					0,
					0,
					0,
					0, 0, 0, 0,
					16,
					0,
					0,
					PFD_MAIN_PLANE,
					0,
					0, 0, 0
				};
			GLuint PixelFormat;

			if(Win32Window->GetMSAA() > 0)
			{
				PixelFormat = GetOptimalPixelFormat(Depth,Win32Window->GetMSAA());
				DescribePixelFormat(DeviceContext,PixelFormat,sizeof(PIXELFORMATDESCRIPTOR),&pfd);
			}
			else
			{
				if (!(PixelFormat = ChoosePixelFormat(DeviceContext,&pfd)))
				{
					JELOG_E("Cant Choose OpenGL Render Context Pixel Format")
					return;
				}
			}

			if(!SetPixelFormat(DeviceContext,PixelFormat,&pfd))
			{
				JELOG_E("Cant Set OpenGL Render Context Pixel Format")
				return;
			}

			if (!(RenderContext = wglCreateContext(DeviceContext)))
			{
				JELOG_E("Cant Create OpenGL Render Context")
				return;
			}

			if((CurrentWorkMode >= OWM_OPENGL3) && EnableForwardContext)
			{
				HGLRC Context_GL_Old = RenderContext;

				if(!wglMakeCurrent( DeviceContext, Context_GL_Old ))
				{
					JELOG_E("Can't activate current context")
					return;
				}

				PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
				s32 attribs[] = {	WGL_CONTEXT_MAJOR_VERSION_ARB,OpenGLMajorVersion,
									WGL_CONTEXT_MINOR_VERSION_ARB,OpenGLMinorVersion,
									WGL_CONTEXT_FLAGS_ARB,WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
									0};

				HGLRC Context_GL3 = NULL;
				if(wglCreateContextAttribsARB)
					Context_GL3 = wglCreateContextAttribsARB(DeviceContext,Context_GL_Old,attribs);

				if(!Context_GL3)
				{
					JELOG_E("Can't create opengl 3 context")
					return;
				}

				if(!wglMakeCurrent( DeviceContext, Context_GL3 ))
				{
					JELOG_E("Can't activate opengl 3 context")
					return;
				}

				if(!wglDeleteContext( Context_GL_Old ))
					JELOG_E("Can't delete old opengl context, maybe mem leak")

				RenderContext = Context_GL3;

				if(!wglMakeCurrent(NULL,NULL))
				{
					JELOG_E("Can't deactivate opengl 3 context")
					return;
				}
			}
			#endif

			#ifdef JE_DEVICE_LINUX
			CurrentGLXContext = glXCreateContext(static_cast<Display*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_DEVICE)),static_cast<XVisualInfo*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_VISUAL_INFO)),0,GL_TRUE);

			if(!CurrentGLXContext)
			{
				JELOG_E("Can't create opengl context")
			}

			if((CurrentWorkMode >= OWM_OPENGL3) && EnableForwardContext)
			{
				GLXContext OldContext = CurrentGLXContext;

				if(!glXMakeCurrent(static_cast<Display*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_DEVICE)),*static_cast<Window*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_WINDOW)),CurrentGLXContext))
				{
					JELOG_E("Cant Activate OpenGL Render Context")
					return;
				}

				s32 attribs[] = {	GLX_CONTEXT_MAJOR_VERSION_ARB,OpenGLMajorVersion,
									GLX_CONTEXT_MINOR_VERSION_ARB,OpenGLMinorVersion,
									GLX_CONTEXT_FLAGS_ARB,GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
									0};

				PFNGLXCREATECONTEXTATTRIBSARBPROC glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC)glXGetProcAddress(reinterpret_cast<const GLubyte*>("glXCreateContextAttribsARB"));

				GLXContext GL3Context = NULL;
				if(glXCreateContextAttribsARB)
					GL3Context = glXCreateContextAttribsARB(static_cast<Display*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_DEVICE)),static_cast<GLXFBConfig*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_FRAMEBUFFER_CONFIG))[0],NULL,true,attribs);

				if(!GL3Context)
				{
					JELOG_E("Can't create opengl 3 context")
					return;
				}

				if(!glXMakeCurrent(static_cast<Display*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_DEVICE)),*static_cast<Window*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_WINDOW)),GL3Context))
				{
					JELOG_E("Cant Activate OpenGL 3 Render Context")
					return;
				}

				glXDestroyContext(static_cast<Display*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_DEVICE)),CurrentGLXContext);

				CurrentGLXContext = GL3Context;

				if (!glXMakeCurrent(static_cast<Display*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_DEVICE)),None,NULL))
				{
					JELOG_E("Cant Deactivate OpenGL Render Context")
					return;
				}
			}
			#endif
		}

		//! Delete OpenGL Context
		void CRenderContext_OpenGL::DeleteOpenGLContext()
		{
			#ifdef JE_DEVICE_WIN

			if(RenderContext)
			{
				if(!wglDeleteContext(RenderContext))
				{
					JELOG_E("Deleting OpenGL Render Context Failed")
				}
				RenderContext = NULL;
			}
			DeviceContext = NULL;

			#endif

			#ifdef JE_DEVICE_LINUX
			if(CurrentGLXContext)
			{
				glXDestroyContext(static_cast<Display*>(RenderWindow->GetWindowApiPointer(device::WAPT_X_DEVICE)),CurrentGLXContext);
				CurrentGLXContext = NULL;
			}
			#endif
		}

		//! Get Optimal Pixel Format
		s32 CRenderContext_OpenGL::GetOptimalPixelFormat(s32 Depth,s32 Samples)
		{
			#ifdef JE_DEVICE_WIN

			HWND FakeWindowHandle;
			HDC FakeDeviceContext;
			HINSTANCE appInstance;
			DEVMODE DefaultMode;

			jeStringd WindowClassName = jeStringd("Enesea_Win32_Window_Fake");
			jeVector2di Size(256,256);

			RECT WindowRect;
			WindowRect.left = 0;
			WindowRect.right = (long)Size.X;
			WindowRect.top = (long)0;
			WindowRect.bottom = (long)Size.Y;

			appInstance = GetModuleHandle(NULL);

			ZeroMemory(&DefaultMode, sizeof(DEVMODE));
			DefaultMode.dmSize = sizeof(DEVMODE);
			EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &DefaultMode);

			WNDCLASS wc;
			wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
			wc.lpfnWndProc = (WNDPROC)DefWindowProc;
			wc.cbClsExtra = 0;
			wc.cbWndExtra = 0;
			wc.hInstance = appInstance;
			wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
			wc.hCursor = LoadCursor(NULL, IDC_ARROW);
			wc.hbrBackground = NULL;
			wc.lpszMenuName = NULL;
			wc.lpszClassName = WindowClassName.c_str();

			if (!RegisterClass(&wc))
			{
				JELOG_E("Failed To Register Window Class")
				return -1;
			}

			DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
			DWORD dwStyle = WS_OVERLAPPEDWINDOW;

			AdjustWindowRectEx(&WindowRect,dwStyle,false,dwExStyle);

			if (!(FakeWindowHandle=CreateWindowEx(dwExStyle,WindowClassName.c_str(),_U("Fake"),dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,0,0,WindowRect.right - WindowRect.left,WindowRect.bottom - WindowRect.top,NULL,NULL,appInstance,NULL)))
			{
				JELOG_E("Window Creation Error")
				return -1;
			}

			if (!(FakeDeviceContext=GetDC(FakeWindowHandle)))
			{
				JELOG_E("Device Context Creation Error")
				return -1;
			}

			static PIXELFORMATDESCRIPTOR pfd =
			{
				sizeof(PIXELFORMATDESCRIPTOR),
				1,
				PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
				PFD_TYPE_RGBA,
				Depth,
				0, 0, 0, 0, 0, 0,
				0,
				0,
				0,
				0, 0, 0, 0,
				16,
				0,
				0,
				PFD_MAIN_PLANE,
				0,
				0, 0, 0
			};

			GLuint FakePixelFormat;

			if (!(FakePixelFormat = ChoosePixelFormat(FakeDeviceContext,&pfd)))
			{
				JELOG_E("Cant Choose OpenGL Render Context Pixel Format")
				return -1;
			}

			if(!SetPixelFormat(FakeDeviceContext,FakePixelFormat,&pfd))
			{
				JELOG_E("Cant Set OpenGL Render Context Pixel Format")
				return -1;
			}

			HGLRC FakeRenderContext;

			if (!(FakeRenderContext = wglCreateContext(FakeDeviceContext)))
			{
				JELOG_E("Cant Create OpenGL Render Context")
				return -1;
			}

			if(!wglMakeCurrent(FakeDeviceContext,FakeRenderContext))
			{
				JELOG_E("Can't activate current context")
				return -1;
			}

			PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
			if(wglChoosePixelFormatARB)
			{

				int NewPixelFormat = 0;
				s32 ValidFormat = 0;
				UINT NumFormats = 0;
				f32 fAttributes[] = {0,0};

				int iAttributes[] = {
					WGL_DRAW_TO_WINDOW_ARB,GL_TRUE,
					WGL_SUPPORT_OPENGL_ARB,GL_TRUE,
					WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB,
					WGL_COLOR_BITS_ARB,24,
					WGL_ALPHA_BITS_ARB,8,
					WGL_DEPTH_BITS_ARB,24,
					WGL_STENCIL_BITS_ARB,0,
					WGL_DOUBLE_BUFFER_ARB,GL_TRUE,
					WGL_SAMPLE_BUFFERS_ARB,GL_TRUE,
					WGL_SAMPLES_ARB,Samples,
					0,0};

				while(!NumFormats)
				{
					ValidFormat = wglChoosePixelFormatARB(FakeDeviceContext,iAttributes,fAttributes,1,&NewPixelFormat,&NumFormats);

					if(!NumFormats)
					{
						iAttributes[19]=iAttributes[19]/2;
						if(iAttributes[19] <= 1)
						{
							JELOG_W(jeStringc("No Support For MSAA ")+jeStringc(Samples))
							break;
						}
					}
				}

				if ((ValidFormat) && (NumFormats > 0))
				{
					FakePixelFormat = NewPixelFormat;
					if(iAttributes[19])
						JELOG(jeStringc("Enable ")+jeStringc(iAttributes[19])+jeStringc(" Multisampling"))
				}
			}

			if(!wglMakeCurrent(NULL,NULL))
			{
				JELOG_E("Can't deactivate opengl 3 context")
				return -1;
			}

			if (!wglDeleteContext(FakeRenderContext))
			{
				JELOG_E("Deleting OpenGL Render Context Failed")
				return -1;
			}

			if (FakeDeviceContext && !ReleaseDC(FakeWindowHandle,FakeDeviceContext))
			{
				JELOG_E("Error at release device context")
				return -1;
			}

			FakeDeviceContext = NULL;

			if(FakeWindowHandle)
			{
				SetWindowLong(FakeWindowHandle,DWL_USER,(LONG)NULL);
				if(!DestroyWindow(FakeWindowHandle))
				{
					JELOG_E("Error at release window handle")
					return -1;
				}
			}

			FakeWindowHandle = NULL;

			if (!UnregisterClass(WindowClassName.c_str(),appInstance))
				JELOG_E("Error at unregister class")

			return FakePixelFormat;

			#else
				return -1;

			#endif
		}

	}
}

#endif
